const ldap = require("ldapjs");
const EventEmitter = require("events").EventEmitter;

function addOrDelMonitor(adConfig) {
  // adConfig = {
  //   hostName: 'localhost',
  //   port: '389',
  //   serverUrl: 'ldap://localhost:389',
  //   bindDN: 'cn=administrator,cn=users,dc=test,dc=authing,dc=com',
  //   baseDN: 'CN=steamory,CN=users,DC=test,DC=authing,DC=com',
  //   password: 'Authing.cn200500404',
  //   changedAttribs: ['name', 'isDeleted', 'uSNChanged'],
  //   // changedAttribs: 'name',
  //   filterStr: 'ObjectClass=*',
  // }
  adConfig = JSON.parse(adConfig)
  const { serverUrl, bindDN, baseDN, password, filterStr } = adConfig;
  const client = ldap.createClient({
    url: serverUrl,
  });

  const objectClassMap = new Map();

  const checkAddAndObj = (pGUIDs, nGUIDs) => {
    // 找出 两者之间的并集 commonSet
    const commonSet = Array.from(pGUIDs).filter((x) => nGUIDs.has(x));
    console.log(pGUIDs.size, nGUIDs.size, commonSet.length)
    // 找出 pGUIDs commonSet 之间的差集 即为 delSet
    const delSet = Array.from(pGUIDs).filter((x) => !(new Set([...commonSet])).has(x));
    // 找出 nGUIDs commonSet 之间的差集 即为 addSet
    const addSet = Array.from(nGUIDs).filter((x) => !(new Set([...commonSet])).has(x));

    // const emiter = new EventEmitter();

    addSet.length > 0 && addSet.forEach(GUID => {
      // console.log(objectClassMap.get(GUID))
      // process.send({
      //   eventType: 'add',
      //   ...(objectClassMap.get(GUID))
      // });
      // monitor.emit('add', objectClassMap.get(GUID));
    });

    delSet.length > 0 && delSet.forEach(GUID => {
      // console.log(objectClassMap.get(GUID))
      process.send({
        eventType: 'del',
        ...(objectClassMap.get(GUID))
      });
      // monitor.emit('del', objectClassMap.get(GUID));
    });
  };

  client.bind(
    bindDN,
    password,
    (err) => {
      // assert.ifError(err);
      console.log(err)
      const opts = {
        filter: adConfig.filterStr,
        scope: "sub",
        attributes: ["objectGUID"],
      };

      const statusEmit = new EventEmitter();

      statusEmit.on("pending", () => { });

      statusEmit.on("start", () => { });

      // 只要当查找一次且成功结束之后 触发该事件 则能检查实体是新增还是删除
      // 所以只需要控制 查找 的间隔时间 就能完成轮询 相关操作
      // 而每次查找的结果的结束的标志 就是事件触发器 被触发 OK 事件， 该事件又会触发 checkAddAndObj 以完成后续操作
      statusEmit.on("ok", (nGUIDs, pGUIDs) => {
        let _pGUIDs = new Set(pGUIDs);
        let _nGUIDs = new Set(nGUIDs);
        if (!inited) {
          // 第一次
          //  _pGUIDs = _nGUIDs;
        } else {
          // checkAddAndObj 其中包含异步操作, 为避免影响下次中搜索结果的保存, 此处 拷贝 pGUIDs, nGUIDs 以进行接下来的操作
          // 至少是第二次
          checkAddAndObj(_pGUIDs, _nGUIDs);
        }
        setTimeout(() => {
          _pGUIDs = _nGUIDs;
          __nGUIDs = new Set();
          search(__nGUIDs, statusEmit, _pGUIDs);
        }, 1000 * 1);
        inited = true;
      });

      const search = (data, emiter, pGUIDs) => {
        client.search(baseDN, opts, (err, res) => {
          emiter.emit("pending");
          // assert.ifError(err);
          emiter.emit("start");
          res.on("searchEntry", (entry) => {
            //   const { attributes } = entry;
            //   const objectGUIDAttribute = attributes.find(
            //     (attr) => attr.type === 'objectGUID'
            //   );
            //   const objectGUID = Buffer.from(
            //     objectGUIDAttribute.buffers[0],
            //     'hex'
            //   ).toString('binary');
            const objectGUID = resolveGUID(entry);
            objectClassMap.set(objectGUID, entry);
            data.add(objectGUID);
          });
          res.on("searchReference", (referral) => {
            console.log("referral: " + referral.uris.join());
          });
          res.on("error", (err) => {
            console.error("error: " + err.message);
          });
          res.on("end", (result) => {
            // console.log("status: " + result.status);
            emiter.emit("ok", data, pGUIDs);
          });
        });
      };

      let nGUIDs = new Set();
      let inited = false;
      const monitorAddAndDelObj = () => {
        search(nGUIDs, statusEmit, []);
      };


      monitorAddAndDelObj();
    }
  );

  function resolveGUID(entry) {
    if (!Array.isArray(entry.attributes))
      throw new Error("Attributes must be an array");

    const binaryGUID = entry.attributes.find(
      (attribute) => attribute.type === "objectGUID"
    ).buffers[0];
    const guidFormat = [
      [3, 2, 1, 0],
      [5, 4],
      [7, 6],
      [8, 9],
      [10, 11, 12, 13, 14, 15],
    ];

    const guidArray = guidFormat.map((part) => {
      const stringPart = part.map((byte) => {
        // If less than 16 add a 0 to the end
        const byteString =
          binaryGUID[byte] < 16
            ? `0${binaryGUID[byte].toString(16)}`
            : binaryGUID[byte].toString(16);

        return byteString;
      });
      return `${stringPart.join("")}`;
    });
    return guidArray.join("-");
  }
}

// const moniter = new EventEmitter();

// addOrDelMonitor('', moniter)


addOrDelMonitor(process.argv[2])